﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Djs.WinForm.Components;
using Djs.Games.DeskGame.GameDefinitions;

namespace Djs.Games.DeskGame.GameComponents
{
    /// <summary>
    /// Třída, která reprezentuje vizuální control, který vykresluje hru (políčka, figurky, interaktivitu)
    /// </summary>
    public class GameArea : LayeredPanel
    {
        #region Konstrukce a proměnné
        public GameArea()
        {
            this.Initialize();
        }
        protected void Initialize()
        {
            this.LayerCount = 4;             // Mějme čtyři vrstvy grafiky: 0=statický podklad + Linky, 1=políčka, 2=figurky neinteraktivní, 3=figurka pohybovaná myší
            this.ResizeRedraw = true;
            this._Game = null;
            this.InitInteractivity();
        }
        /// <summary>
        /// true, pokud je Datová základna hry korektní
        /// </summary>
        protected bool _ContainValidDefinition { get { return this._Game != null && this._Game.IsValid; } }
        /// <summary>
        /// Datová základna hry
        /// </summary>
        protected IGame _Game;
        #endregion
        #region Public property
        /// <summary>
        /// Definice hry.
        /// Může být null.
        /// </summary>
        public IGame GameDefinition
        {
            get
            {
                return this._Game;
            }
            set
            {
                if (this._Game != null)
                    this._Game.ReDrawLayer -= new ReDrawLayerEventHandler(_Game_ReDrawLayer);
                
                this._Game = value;

                if (this._Game != null)
                    this._Game.ReDrawLayer += new ReDrawLayerEventHandler(_Game_ReDrawLayer);

                this.Refresh();
            }
        }
        #endregion
        #region Interaktivita
        #region Základ interaktivity - primární eventy, rozcestníky
        /// <summary>
        /// Inicializuje plochu z hlediska interaktivity
        /// </summary>
        protected void InitInteractivity()
        {
            this._MouseDragFenderSize = System.Windows.Forms.SystemInformation.DragSize;
            this.MouseMove += new System.Windows.Forms.MouseEventHandler(GameArea_MouseMove);
            this.MouseDown += new System.Windows.Forms.MouseEventHandler(GameArea_MouseDown);
            this.MouseUp += new System.Windows.Forms.MouseEventHandler(GameArea_MouseUp);
        }
        private void GameArea_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (!this._ContainValidDefinition) return;
            PointF visualPoint = e.Location;
            switch (e.Button)
            {
                case System.Windows.Forms.MouseButtons.None:
                    this._IntMouseMoveNone(visualPoint);
                    break;
                case System.Windows.Forms.MouseButtons.Left:
                    this._IntMouseMoveLeft(visualPoint);
                    break;
            }
            this.ReDrawInvalidLayers();
        }
        private void GameArea_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            PointF visualPoint = e.Location;
            switch (e.Button)
            {
                case System.Windows.Forms.MouseButtons.Left:
                    this._IntMouseDownLeft(visualPoint);
                    break;
            }
            this.ReDrawInvalidLayers();
        }
        private void GameArea_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            if (!this._ContainValidDefinition) return;
            PointF visualPoint = e.Location;
            switch (this.CurrentMouseState)
            {
                case MouseState.LeftDown:
                    this._IntMouseUpLeft(visualPoint);
                    break;
                case MouseState.DragItem:
                    this._IntItemMoveEnd(visualPoint);
                    break;
                case MouseState.DragFrame:
                    this._IntAreaMoveEnd(visualPoint);
                    break;
            }
            this.CurrentMouseState = MouseState.OnControl;
            this.ReDrawInvalidLayers();
        }
        private void _IntMouseMoveNone(PointF visualPoint)
        {
            this._DetectCurrentItemOnPoint(visualPoint);
        }
        private void _IntMouseDownLeft(PointF visualPoint)
        {
            // Stiskla se myš: naposledy provedu detekci prvku pod myší (včetně případné změny), ale zatím nebudu kreslit control:
            this._DetectCurrentItemOnPoint(visualPoint);
            if (this.HasActiveItem)
                this._IntItemMouseDownLeft(visualPoint);
            else
                this._IntAreaMouseDownLeft(visualPoint);

            this._MouseDownPoint = visualPoint;
            this._MouseDragFenderBound = visualPoint.FromCenter(this._MouseDragFenderSize);
            this.CurrentMouseState = MouseState.LeftDown;
        }
        private void _IntMouseUpLeft(PointF visualPoint)
        {
            // Uvolnila se myš, na tomtéž místě kde se stiskla = jde o LeftClick:

            this.CurrentMouseState = MouseState.OnControl;
        }
        private void _IntMouseMoveLeft(PointF visualPoint)
        {
            if (this.CurrentMouseState == MouseState.LeftDown)
            {   // Nyní jsme ve stavu, kdy se myš stiskla (ještě není prováděn pohyb).
                // Detekujeme, zda myš je dosud v _MouseDragFenderBound: pokud ano, nic se neděje:
                if (this._MouseDragFenderBound.Contains(visualPoint))
                    return;

                // Myš se pohnula nad rámec FenderBound => začíná přesouvání, zjistíme zda se přesouvá prvek nebo se rámuje prostor:
                if (this.HasActiveItem && this.CurrentActiveItem.EnableAction(ActionType.MoveBegin))
                {
                    this._IntItemMoveBegin(visualPoint);
                    this.CurrentMouseState = MouseState.DragItem;
                }
                else if (this._Game.EnableAction(ActionType.MoveBegin))
                {
                    this._IntAreaMoveBegin(visualPoint);
                    this.CurrentMouseState = MouseState.DragFrame;
                }
                else
                {
                    this.CurrentMouseState = MouseState.DragNone;
                }
            }

            // Pohyb už je zahájen (buď nyní, anebo dříve), zajistíme provedení vlastního pohybu:
            switch (this.CurrentMouseState)
            {
                case MouseState.DragItem:
                    this._IntItemMoveRun(visualPoint);
                    break;
                case MouseState.DragFrame:
                    this._IntAreaMoveRun(visualPoint);
                    break;
            }
        }
        #endregion
        #region Konkrétní interaktivita pro Area
        private bool _IntAreaMouseDownLeft(PointF visualPoint)
        {
            return true;
        }
        private bool _IntAreaMoveBegin(PointF visualPoint)
        {
            return true;
        }
        private bool _IntAreaMoveRun(PointF visualPoint)
        {
            return true;
        }
        private bool _IntAreaMoveEnd(PointF visualPoint)
        {
            return true;
        }
        #endregion
        #region Konkrétní interaktivita pro IItem
        private void _IntItemMouseEnter(PointF visualPoint)
        {
            this._IntItemRunAction(this.CurrentActiveItem, ActionType.MouseEnter, visualPoint);
        }
        private void _IntItemMouseDownLeft(PointF visualPoint)
        {
            this._MouseDownItemOffset = visualPoint.Sub(this.CurrentActiveItem.VisualCenter);
        }
        private void _IntItemMoveBegin(PointF visualPoint)
        {
            this._MouseDownItemOffset = visualPoint.Sub(this.CurrentActiveItem.VisualCenter);
            this._IntItemRunAction(this.CurrentActiveItem, ActionType.MoveBegin, visualPoint);
        }
        private void _IntItemMoveRun(PointF visualPoint)
        {
            PointF visualCenter = visualPoint.Sub(this._MouseDownItemOffset);
            this._IntItemRunAction(this.CurrentActiveItem, ActionType.MoveRun, visualCenter);
        }
        private void _IntItemMoveEnd(PointF visualPoint)
        {
            PointF visualCenter = visualPoint.Sub(this._MouseDownItemOffset);
            this._IntItemRunAction(this.CurrentActiveItem, ActionType.MoveEnd, visualCenter);
        }
        private void _IntItemMouseLeave(PointF visualPoint)
        {
            this._IntItemRunAction(this.CurrentActiveItem, ActionType.MouseLeave, visualPoint);
        }
        private void _IntItemRunAction(IItem item, ActionType action, PointF visualPoint)
        {
            int oldLayer = item.VisualLayer;
            bool isChange = item.RunAction(action, visualPoint);
            int newLayer = item.VisualLayer;
            if (isChange)
                this.InvalidateLayer(oldLayer, newLayer);
        }
        #endregion
        #region Podpůrné metody, stavové property a proměnné
        /// <summary>
        /// Metoda zjistí, zda na pozici (visualPoint) se nachází prvek, zda je jiný než CurrentActiveItem.
        /// Pokud ano, pak zajistí korektní změnu (pro starý prvek zavolá RunAction(MouseLeave), pro nový prvek zavolá RunAction(MouseEnter).
        /// Zajistí, že v this.CurrentActiveItem bude prvek odpovídající danému bodu.
        /// Podle požadavku reDrawIfNeed zajistí v případě potřeby ReDraw controlu.
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <param name="reDrawIfNeed"></param>
        /// <returns></returns>
        private void _DetectCurrentItemOnPoint(PointF visualPoint)
        {
            IItem previousCell = this.CurrentActiveItem;
            IItem currentCell = this._Game.FindItemOnPoint(visualPoint, ActionType.MouseEnter);
            if (Object.ReferenceEquals(previousCell, currentCell))
                return;

            // Došlo ke změna políčka pod myší, zjistíme zda je třeba invalidovat jejich vrstvy:
            if (this.CurrentActiveItem != null)
                this._IntItemMouseLeave(visualPoint);

            this.CurrentActiveItem = currentCell;
            if (this.CurrentActiveItem != null)
                this._IntItemMouseEnter(visualPoint);
        }
        /// <summary>
        /// Obsahuje true, pokud máme aktivní prvek v CurrentActiveItem.
        /// </summary>
        protected bool HasActiveItem { get { return (this.CurrentActiveItem != null); } }
        /// <summary>
        /// Aktivní prvek (je pod myší).
        /// Zda je myš stisknutá, lze určit podle proměnné CurrentMouseState.
        /// </summary>
        protected IItem CurrentActiveItem { get; set; }
        /// <summary>
        /// Aktuální stav myši.
        /// </summary>
        protected MouseState CurrentMouseState { get; set; }
        /// <summary>
        /// Bod, na němž se stiskla myš
        /// </summary>
        private PointF _MouseDownPoint;
        /// <summary>
        /// Offset bodu, na němž se stiskla myš, proti bodu Item.VisualCenter
        /// </summary>
        protected PointF _MouseDownItemOffset { get; set; }
        /// <summary>
        /// <para>
        /// Velikost rámečku _MouseDragFenderBound. Inicializuje se při inicializaci this, z hodnoty System.Windows.Forms.SystemInformation.DragSize.
        /// </para>
        /// <para>
        /// Gets the width and height of a rectangle centered on the point the mouse button was pressed, within which a drag operation will not begin.
        /// </para>
        /// <para>
        /// Return: A System.Drawing.Size that specifies the area of a rectangle, in pixels, centered on the point the mouse button was pressed, within which a drag operation will not begin.
        /// </para>
        /// </summary>
        protected SizeF _MouseDragFenderSize { get; set; }
        /// <summary>
        /// Malinký rámeček okolo souřadnice myši, na níž se myš stiskla.
        /// V tomto rámečku se může myš pohybovat, aniž by začalo přesouvání / tažení.
        /// Jakmile je myš stisknutá a pohne se mimo tento rámeček, začne akce přesouvání.
        /// </summary>
        protected RectangleF _MouseDragFenderBound { get; set; }
        #endregion
        #endregion
        #region Kreslení hrací plochy - kreslí na základě invalidovaných vrstev.
        /// <summary>
        /// Zajistí vykreslení těch vrstev, které jsou nyní invalidovány.
        /// Po vykreslení nastaví všechny vrstvy jako validní.
        /// </summary>
        protected void ReDrawInvalidLayers()
        {
            List<int> layersToDraw = new List<int>();
            if (!this.Layer0Valid) layersToDraw.Add(0);
            if (!this.Layer1Valid) layersToDraw.Add(1);
            if (!this.Layer2Valid) layersToDraw.Add(2);
            if (!this.Layer3Valid) layersToDraw.Add(3);
            
            if (layersToDraw.Count > 0)
                this.ReDraw(null, layersToDraw);
        }
        /// <summary>
        /// Zajistí invalidaci daných vrstev.
        /// Metody samy by neměly pracovat s proměnnými this.Layer0Valid atd.
        /// </summary>
        /// <param name="layers"></param>
        protected void InvalidateLayer(params int[] layers)
        {
            foreach (int layer in layers)
            {
                switch (layer)
                {
                    case 0:
                        this.Layer0Valid = false;
                        break;
                    case 1:
                        this.Layer1Valid = false;
                        break;
                    case 2:
                        this.Layer2Valid = false;
                        break;
                    case 3:
                        this.Layer3Valid = false;
                        break;
                }
            }
        }

        /// <summary>
        /// Stav vrstvy 0 = zcela statický podklad (okraje, podkladové obrázky, možná Linky).
        /// true = pokud tato grafická vrstva obsahuje platná data, pak ji metoda ReDrawInvalidLayers() nebude překreslovat.
        /// false = vrstvu je třeba překreslit.
        /// </summary>
        protected bool Layer0Valid;
        /// <summary>
        /// Stav vrstvy 1 = málo dynamický podklad (políčka, mohou být interaktivní)
        /// true = pokud tato grafická vrstva obsahuje platná data, pak ji metoda ReDrawInvalidLayers() nebude překreslovat.
        /// false = vrstvu je třeba překreslit.
        /// </summary>
        protected bool Layer1Valid;
        /// <summary>
        /// Stav vrstvy 2 = běžně dynamický motiv (figurky v klidovém stavu)
        /// true = pokud tato grafická vrstva obsahuje platná data, pak ji metoda ReDrawInvalidLayers() nebude překreslovat.
        /// false = vrstvu je třeba překreslit.
        /// </summary>
        protected bool Layer2Valid;
        /// <summary>
        /// Stav vrstvy 3 = aktivní pohybující se figurky
        /// true = pokud tato grafická vrstva obsahuje platná data, pak ji metoda ReDrawInvalidLayers() nebude překreslovat.
        /// false = vrstvu je třeba překreslit.
        /// </summary>
        protected bool Layer3Valid;
        /// <summary>
        /// Háček, který je volán z base třídy při kreslení obsahu controlu do vrstev.
        /// Pozor: kreslení není volané vždy jen z this metody ReDrawInvalidLayers(), ale v případě Resize prvku se volá přímo z base třídy, pak se kreslí všechny vrstvy.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintLayers(LayeredPaintEventArgs e)
        {
            base.OnPaintLayers(e);

            if (!this._ContainValidDefinition) return;

            this._Game.Paint(this, e, this.ClientRectangle);

            this.Layer0Valid = true;
            this.Layer1Valid = true;
            this.Layer2Valid = true;
            this.Layer3Valid = true;
        }
        #endregion
        #region Podpora pro animaci
        /// <summary>
        /// Eventhandler pro událost, kdy Game sama chce překreslit hrací plochu od určité vrstvy.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _Game_ReDrawLayer(object sender, ReDrawLayerEventArgs e)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new Action<ReDrawLayerEventArgs>(this._Thread_ReDrawLayer), e);
            else
                this._Thread_ReDrawLayer(e);
        }
        private void _Thread_ReDrawLayer(ReDrawLayerEventArgs e)
        {
            this.InvalidateLayer(e.ReDrawLayer);
            this.ReDrawInvalidLayers();
        }
        #endregion
    }
    #region class 
    public delegate void ReDrawLayerEventHandler(object sender, ReDrawLayerEventArgs e);
    public class ReDrawLayerEventArgs : EventArgs
    {
        public ReDrawLayerEventArgs(int reDrawLayer)
        {
            this.ReDrawLayer = reDrawLayer;
        }
        public int ReDrawLayer { get; private set; }
    }
    #endregion
    #region interface IGame : předpis vlastností a metod pro prvky, které reprezentují hru (její data a pravidla)
    /// <summary>
    /// IGame : předpis vlastností a metod pro prvky, které reprezentují hru (její data a pravidla)
    /// </summary>
    public interface IGame
    {
        /// <summary>
        /// Obsahuje true, pokud je deklarace hry platná a lze s ní pracovat
        /// </summary>
        bool IsValid { get; }
        /// <summary>
        /// Vrátí true, pokud hra podporuje danou akci
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        bool EnableAction(ActionType action);
        /// <summary>
        /// Najde a vrátí prvek, který se nachází na daním vizuálním bodu, a který povoluje provedení dané akce.
        /// Pokud nenajde, vrátí null.
        /// Pokud chceme hledat prvek bez ohledu na akci, zadá se akce None.
        /// </summary>
        /// <param name="visualPoint"></param>
        /// <param name="enabledAction"></param>
        /// <returns></returns>
        IItem FindItemOnPoint(PointF visualPoint, ActionType enabledAction);
        /// <summary>
        /// Zajistí vykreslení požadovaných vrstev hrací plochy (v režimu Layered).
        /// </summary>
        /// <param name="gameArea"></param>
        /// <param name="e"></param>
        /// <param name="visualArea"></param>
        void Paint(GameArea gameArea, LayeredPaintEventArgs e, Rectangle visualArea);
        /// <summary>
        /// Událost, kdy hra sama chce překreslit určitou vrstvu.
        /// Typicky při animaci.
        /// </summary>
        event ReDrawLayerEventHandler ReDrawLayer;

    }
    #endregion
    #region interface IItem : předpis vlastností a metod pro prvky, které se mohou vyskytovat na hrací ploše.      enum ActionType, MouseState
    /// <summary>
    /// IItem : předpis vlastností a metod pro prvky, které se mohou vyskytovat na hrací ploše.
    /// </summary>
    public interface IItem
    {
        /// <summary>
        /// Aktuální číslo vrstvy, do které se prvek kreslí.
        /// GameArea používá 4 vrstvy: 0=pozadí a statické prvky, 1=políčka, 2=figurky na políčku, 3=figurky pohybované myší.
        /// GameArea v rámci interaktivity určuje, kterých vrstev se změna prováděná uživatelem týká, následně volá ReDraw() pro tyto vrstvy.
        /// GameDefinition sestaví soupisy položek pro kreslení podle jejich vrstvy (tato property), a zajistí korektní vykreslování jednotlivých vrstev.
        /// </summary>
        int VisualLayer { get; }
        /// <summary>
        /// Aktuální pozice středu objektu, využívá se při Drag, v akci ActionType.MoveRun a ActionType.MoveEnd se předává nově dopočítaný vizuální střed objektu.
        /// </summary>
        PointF VisualCenter { get; }
        /// <summary>
        /// Vykreslí objekt. On sám musí vědět, jak a kam.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Paint(GameArea gameArea, Graphics graphics);
        /// <summary>
        /// Zjistí, zda prvek se nachází z hlediska interaktivity na dané souřadnici.
        /// Vrátí true = na daném bodu se nachází tento objekt.
        /// </summary>
        /// <param name="visualPoint"></param>
        bool IsOnPoint(PointF visualPoint);
        /// <summary>
        /// Zjistí, zda tento prvek v této chvíli podporuje danou akci.
        /// Vrátí true = na akce je povolená.
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        bool EnableAction(ActionType action);
        /// <summary>
        /// Provede danou akci. Předává informaci o nové vizuální pozici středu.
        /// Vrací true = když je třeba překreslit celý obsah GameArea.
        /// </summary>
        /// <param name="action"></param>
        /// <param name="visualCenter"></param>
        /// <returns></returns>
        bool RunAction(ActionType action, PointF visualCenter);
    }
    /// <summary>
    /// Druhy akcí
    /// </summary>
    public enum ActionType
    {
        /// <summary>Žádná akce</summary>
        None = 0,
        /// <summary>Vykreslení prvku (nastavuje tak viditelné / neviditelné prvky). Akce pro kreslení ale nechodí do metody IItem.RunAction(), ale do metody IItem.Paint()</summary>
        Paint,
        /// <summary>Myš vstoupila na prvek, není zmáčknutá.</summary>
        MouseEnter,
        /// <summary>Myš není zmáčknutá, opouští prvek.</summary>
        MouseLeave,
        /// <summary>Myš se stiskla a uvolnila. DoubleClick není podporován.</summary>
        MouseClick,
        /// <summary>Začíná přesouvání myší. Prvek je na původní souřadnici. Následovat bude akce MoveRun, již pro novou souřadnici.</summary>
        MoveBegin,
        /// <summary>Provádí se přesouvání myší. Je předána nová souřadnice, ale prvek se na ní musí přemístit sám</summary>
        MoveRun,
        /// <summary>Skončil pohyb myší</summary>
        MoveEnd,
        /// <summary>Začíná animace. V metodě IItem.EnableAction() ji lze povolit nebo odmítnout. V metodě IItem.RunAction() lze změnit vzhled a přeřadit prvek do odpovídající vrstvy (Layer).</summary>
        AnimationBegin,
        /// <summary>Probíhá animace, je předána souřadnice vizuálního středu. Prvek sám si jen převezme souřadnici, ale fyzické kreslení nevyvolává - to řeší animátor (GameAnimator).</summary>
        AnimationRun,
        /// <summary>Končí animace. V metodě IItem.RunAction() by se měla vynulovat explicitní vizuální souřadnice, změnit vzhled a Layer.</summary>
        AnimationEnd,
        /// <summary>Požadavek na zrušení animace. Přichází z UI, jde do IGame.RunAction(), jde do GameAnimation.</summary>
        AnimationCancel
    }
    /// <summary>
    /// Stav myši z pohledu editační plochy
    /// </summary>
    public enum MouseState
    {
        /// <summary>Nijaký</summary>
        None = 0,
        /// <summary>Myš se pohybuje nad controlem</summary>
        OnControl,
        /// <summary>Myš je stisknuta</summary>
        LeftDown,
        /// <summary>Myš framuje prostor (stisknuta mimo pohyblivý prvek, pohybuje se)</summary>
        DragFrame,
        /// <summary>Myš přesouvá prvek</summary>
        DragItem,
        /// <summary>Myš se sice přesouvá, ale nic nedělá - čeká se na zvednutí myši (ani prvek se nechce hýbat, ani plocha se nemá framovat)</summary>
        DragNone
    }
    #endregion
}
